package danceDoll;

import bvh.BVHAnimation;
import bvh.BVHBone;
import bvh.BVHChannel;
import java.util.ArrayList;
import java.util.List;

/**
 * Creates a Transition between two BVHController instances
 * @author Ben Blume
 */
public class Transition {

    public Initialize init;
    public BVHController next, newTransition;
    public int currentFrame;
    public static float[] transition;
    
    /**
     * creates a transition inbetween the values of two float arrays by combining 
     * them and then replacing the values with a bezier curve based on the values
     * @param current
     * @param next
     * @param bone
     * @return transition
     */
    public static float[] makeTransition(float[] current, float[] next, BVHBone bone) {
        transition = new float[current.length + next.length];
        float[] values = combineArrays(current, next);
        if(bone.getName().matches("RThigh") | bone.getName().matches("LThigh")){
            return values;
        }
        for (int i = 0; i < values.length; i++) {
            float t = (1.0f / (values.length - 1)) * i;
            bezier(values, t, i);
        }
        return transition;
    }

    /**
     * Requires current animation and next animation. Makes a transition for 20 frames and saves them at the end from the file.
     * @param current current animation BVHController
     * @param next next animation  BVHController
     * @param currentFrame frameNummer to start with
     * @return BVHControl
     */
    public BVHController makeTransition(BVHController current, BVHController next, int currentFrame) {
        newTransition = new BVHController();
        newTransition.data = current.data;
        this.currentFrame = currentFrame;
        this.next = next;
        newTransition.animation = new BVHAnimation();
        newTransition.chkFile = true;
        newTransition.animation.setFrameTime(current.animation.getFrameTime());
        newTransition.animation.setNbFrames(current.animation.getNbFrames() + 20);
        newTransition.animation.setHierarchy(current.animation.getHierarchy());

        traverseBVH(current.animation.getHierarchy());

        return newTransition;
    }

    /**
     * Recursive Method to calculate for every BVHBone the transition values
     * @param bvhBone BVHBone
     */
    public void traverseBVH(BVHBone bvhBone) {
        if (bvhBone != null && !bvhBone.getName().equals("Site")) {
            BVHBone nextBone = getBVHBone(bvhBone.getName(), next.animation.getHierarchy());

            float[][] tmp = new float[bvhBone.getChannels().size()][20];
            for (int j = 0; j < bvhBone.getChannels().size(); j++) {
                float[] currentAnim = new float[10];
                float[] nextAnim = new float[10];
                for (int i = 0; i < currentAnim.length; i++) {
                    if (bvhBone.getChannels().get(j).getValues().get(currentFrame + i) != null) {
                        currentAnim[i] = bvhBone.getChannels().get(j).getValues().get(currentFrame + i);
                    }
                }
                for (int i = 0; i < nextAnim.length; i++) {
                    if (nextBone.getChannels().get(j).getValues().get(i + i) != null)
                        nextAnim[i] = nextBone.getChannels().get(j).getValues().get(i + 1);
                }
                
                tmp[j] = makeTransition(currentAnim, nextAnim, bvhBone);
            }

            BVHBone transBone = getBVHBone(bvhBone.getName(), newTransition.animation.getHierarchy());

            for (int i = 0; i < transition.length; i++) {
                if (transBone.getChannels() != null) {
                    int k = 0;
                    for (BVHChannel bvhChannel : transBone.getChannels()) {

                        if (bvhChannel.getValues() == null || i== 0) {
                            bvhChannel.setValues(new ArrayList<Float>());
                        }
                        bvhChannel.getValues().add(tmp[k][i]);
                        k++;
                    }
                }
            }

            List<BVHBone> children = bvhBone.getChildren();
            if (children != null) {
                for (BVHBone b : children) {
                    traverseBVH(b);
                }
            }
        }
    }

    /**
     * Searches in the BVHBone-Hirachy for the given Bone-name and returns the BVHBone
     * @param name Name to search for
     * @param b bone to start (eg Hip)
     * @return the Bone
     */
    public BVHBone getBVHBone(String name, BVHBone b) {
        if (b.getName().equals(name)) {
            return b;
        }

        BVHBone found = null;
        if (b.getChildren() != null) {
            for (BVHBone bb : b.getChildren()) {
                if (bb.getName().equals(name)) {
                    found = bb;
                } else {
                    if (bb.getChildren() != null) {
                        for (BVHBone b2 : bb.getChildren()) {
                            if (b2.getName().equals(name)) {
                                found = b2;
                            } else if (found == null) {
                                found = getBVHBone(name, b2);
                            }
                        }
                    }
                }
            }
        }
        return found;
    }

    /**
     * Calculates the Bezier-Transform
     * @param values
     * @param t
     * @param i 
     */
    public static void bezier(float[] values, float t, int i) {
        if (values.length == 2) {
            float x = (1 - t) * values[0] + t * values[1];
            transition[i] = x;
        } 
        else {
            int n = values.length - 1;
            float[] newValues = new float[n];
            for (int j = 0; j < n; j++) {
                newValues[j] = (1 - t) * values[j] + t * values[j + 1];
            }
            bezier(newValues, t, i);
        }
    }
    
    
    /**
     * Combines two float arrays into one by adding the values of array b to 
     * the end of array one and returning the comined array
     * @param a first Array
     * @param b second Array
     * @return new Array
     */
    public static float[] combineArrays(float[] a, float[] b) {
        float[] result = new float[a.length + b.length];

        for (int i = 0; i < a.length; i++) {
            result[i] = a[i];
        }

        for (int i = 0; i < b.length; i++) {
            result[a.length + i] = b[i];
        }
        return result;
    }
}